#!/usr/bin/env python
from xmlobject import XMLFile
import sys
import types

class Id(object):
    def __init__(self, context, id):
        self.id = id
        self.context = context
    def __eq__(self, other):
        if not type(other) == Id: raise Exception('Not an Id', other)
        return self.id == other.id and self.context == other.context
    def __hash__(self):
        return id(self.context) ^ self.id 

class Context(dict):
    def __init__(self, name):
        self.name = name
    def add(self, member):
        self[member.id] = member             
        if not hasattr(self, member.__class__.__name__): setattr(self, member.__class__.__name__, {})
        getattr(self, member.__class__.__name__)[member.id] = member
        if isinstance(member, Struct) or isinstance(member, Type) or isinstance(member, Namespace):
            for id in self.Namespace:
                namespace = self.Namespace[id]
                if member.id in namespace.members:
                    member.namespace = namespace
                    break
            if not member.namespace: member.namespace = self.globalns
    def get(self, name):
        bits = name.split('::')
        del bits[0]
        result = self.globalns
        for bit in bits:
            result = result.names[bit]
        return result
    def getid(self, id):
        if type(id) in (types.StringType, unicode):
            return Id(self, int(id.replace('_','').replace('c','')))
        if type(id) == types.IntType:
            return Id(self, id)
        raise Exception('Unsupported type '+str(type(id)))
    def get_globalns(self):
        return self[self.getid(1)]
    globalns = property(get_globalns)
    def __eq__(self, other):
        return id(self) == id(other)
        
class Member(object):
    def __init__(self, context):
        self.__namespace = None
        context.add(self)
    def get_namespace(self):
        if isinstance(self, Namespace): retval = [self]
        else: retval = []
        namespace = self.__namespace
        while namespace != None:
            retval.append(namespace)
            namespace = namespace.__namespace
        retval.reverse()
        return retval
    def set_namespace(self, namespace):
        self.__namespace = namespace    
    namespace = property(get_namespace, set_namespace)
    def resolve(self, context): pass
    def display(self): return str(self)    
    def get_fulltype(self):
        if isinstance(self, Type):
            type = self
        else:
            type = self.type
        types = []
        while not type in types and type != None:
            types.append(type)
            type = type.type

        return types
    fulltype = property(get_fulltype)
    
class Namespace(Member, dict):
    def __init__(self, xml, context):
        self.xml = xml
        self.name = xml.name.encode('utf-8')
        self.id = context.getid(xml.id)
        self.names = {}
        if self.id.id == 1: 
            self.name = ''
        self.members = []
        for member in xml.members.split(' '):
            if member == '': continue
            self.members.append(context.getid(member))
        Member.__init__(self, context)
        if not len(self.namespace) < 2:
            self.namespace[-2].names[self.name] = self
        dict.__init__(self)
    def get_fullname(self):
        s = ''
        for namespace in self.namespace:
            s += '%s::' % (namespace.name)
        return s.rstrip('::')
    fullname = property(get_fullname)
    def __repr__(self):
        return self.name
    def __eq__(self, other):
        return id(self) == id(other)    
        
class Type(Member):
    def __init__(self, xml, context):
        self.xml = xml
        self.id = context.getid(xml.id)
        Member.__init__(self, context)
    def get_fullname(self):
        return str(self)
    fullname = property(get_fullname)
    def __repr__(self):
        if hasattr(self, 'name'): return self.name
        if hasattr(self, 'typename'): return self.typename
    def display(self, indent = 0):
        s = ''
        for type in self.fulltype:
            s += '%s ->' % (type)
        return self.fullname+' = '+s.rstrip(' ->')
    def resolve(self, context):
        pass
        #if hasattr(self, 'name'): self.namespace[-1].names[self.name] = self        
        
class FundamentalType(Type):
    def __init__(self, xml, context):
        self.type = self
        self.name = xml.name.encode('utf-8')
        Type.__init__(self, xml, context)  
                
class Typedef(Type):
    def __init__(self, xml, context):
        self.name = xml.name.encode('utf-8')
        Type.__init__(self, xml, context)
    def resolve(self, context):
        Type.resolve(self, context)
        self.type = context[context.getid(self.xml.type)]

class FunctionType(Type):
    def __init__(self, xml, context):
        self.type = self
        Type.__init__(self, xml, context)
    def resolve(self, context):
        Type.resolve(self, context)
                
class ArrayType(Type):
    def __init__(self, xml, context):
        Type.__init__(self, xml, context)
    def get_typename(self):
        return '%s []' % (self.type.fullname)
    typename = property(get_typename)
    def resolve(self, context):
        self.type = context[context.getid(self.xml.type)]
        Type.resolve(self, context)
        
class Enumeration(Type):
    def __init__(self, xml, context):
        self.type = self
        self.name = xml.name.encode('utf-8')
        Type.__init__(self, xml, context)
        
class PointerType(Type):
    def __init__(self, xml, context):
        Type.__init__(self, xml, context)
    def get_typename(self):
        return '%s *' % (self.type.fullname)
    typename = property(get_typename)        
    def resolve(self, context):
        self.type = context[context.getid(self.xml.type)]
        Type.resolve(self, context)          
        
class ReferenceType(Type):
    def __init__(self, xml, context):
        Type.__init__(self, xml, context)
    def get_typename(self):
        return '%s &' % (self.type.fullname)
    typename = property(get_typename)        
    def resolve(self, context):
        self.type = context[context.getid(self.xml.type)]
        Type.resolve(self, context)
                
class Struct(Member, dict):
    def __init__(self, xml, context):
        self.xml = xml
        self.name = xml.name.encode('utf-8')
        self.id = context.getid(xml.id)
        self.fields = []
        self.type = self
        self.incomplete = True
        if not hasattr(xml, 'incomplete'):
            self.incomplete = False 
            self.size = int(xml.size)/8
        Member.__init__(self, context)
        dict.__init__(self)
    def get_fullname(self):
        return '%s::%s' % (self.namespace[-1].fullname, self)
    fullname = property(get_fullname)
    def __repr__(self):
        return self.name
    def field_display(self, indent=0, offset=0):
        s=''
        x = str(self.fullname)
        y = x.startswith("::ilrt::Runtime")
        if y: print x
        for field in self.fields:
            if y: print '   ',field.name,field.type,type(field.type),field.display(indent, offset)
            if isinstance(field.type, Class) or isinstance(field.type, Struct):
                if y: print 'yo'
                s += '\n%s%s'%(indent*' ', field.type.display(indent, field.offset + offset))
            else:
                s += '\n%s%s'%(indent*' ', field.display(indent, field.offset + offset))
        return s
    def display(self, indent=0, offset=0):
        return '%s%s (%d @ %d) {%s\n%s}' % (indent*' ',self.fullname, self.size, offset, self.field_display(indent+1, offset), indent*' ')
    def resolve(self, context):
        self.namespace[-1].names[self.name] = self
        self.file = context.File[self.xml.file]
        self.line = int(self.xml.line)
        def field_sort(a,b):
            return b.offset-a.offset
        self.fields.sort(field_sort)
    def __eq__(self, other):
        return id(self) == id(other)
        
class BaseClass(object):
    def __init__(self, klass, offset):
        self.klass = klass
        self.offset = offset
    def display(self, indent=0, offset=0):
        return '%s' % (self.klass.display(indent+1, self.offset + offset))    

class Argument(object):
    def __init__(self, name, type):
        self.name = name
        self.type = type
                
class Method(Member):
    def __init__(self, xml, context):
        self.name = xml.name.encode('utf-8')
        self.xml = xml
        self.id = context.getid(xml.id)
        self.arguments = []
        Member.__init__(self, context)
    def resolve(self, context):
        self.returns = context[context.getid(self.xml.returns)]
        self.parent = context[context.getid(self.xml.context)]
        self.file = context.File[self.xml.file]
        self.line = int(self.xml.line)
        self.parent.methods.append(self)
        if hasattr(self.xml, 'Argument'):
            if not type(self.xml.Argument) == types.ListType: arguments = [self.xml.Argument]
            else: arguments = self.xml.Argument
            for argument in arguments:
                if not hasattr(argument, 'name'): raise Exception('Unnamed argument in function %s:%s' % (self.file,self.line))
                self.arguments.append(Argument(argument.name, context[context.getid(argument.type)]))
    def display(self, indent=0):
        s = '%s%s %s (' % (indent*' ', self.returns.fullname, self.name)
        for argument in self.arguments:
            s += '%s %s,' % (argument.type.fullname, argument.name)
        return s.rstrip(',') + ')'
        
class Class(Struct):
    def __init__(self, xml, context):
        Struct.__init__(self, xml, context)
        if hasattr(xml, 'abstract'): self.abstract = True
        else: self.abstract = False
        self.bases = []
        self.methods = []
        self.serializable = False
        self.debuggable = False
    def resolve(self, context):
        Struct.resolve(self, context)
        if hasattr(self.xml, 'Base'):
            if not type(self.xml.Base) == types.ListType: bases = [self.xml.Base]
            else: bases = self.xml.Base
            for base in bases:
                base = BaseClass(context[context.getid(base.type)], int(base.offset))
                self.bases.append(base)
                
        def base_sort(a,b):
            return a.offset-b.offset
        self.bases.sort(base_sort) 
    def display(self, indent=0, offset = 0):
        if not self.incomplete:
            s = '%s%s (%d @ %d) {' % (indent*' ', self.fullname, self.size, offset)
        else:
            s = '%s%s (incomplete) {' % (indent*' ', self.fullname)
        if self.abstract: s += '\n%svtable pointer @ %d' % ((indent+1)*' ', offset)
        s += '\n%s--Bases--\n' % (indent*' ')
        for base in self.bases:
            s += base.display(indent+1, offset)
        s += '\n%s--Fields--\n' % (indent*' ')
        s += self.field_display(indent+1, offset)
        for method in self.methods:
            s += '\n%s' % (method.display(indent+1))
        s += '\n%s}' % (indent*' ')
        return s
    def inherits(self, klass, recursive=False):
        for base in self.bases:
            if base.klass == klass:
                print base.klass,klass,base.klass == klass 
                return True
            if recursive: return base.klass.inherits(klass, recursive)    
        
class Field(Member):
    def __init__(self, xml, context):
        self.xml = xml
        self.name = xml.name.encode('utf-8')
        self.id = context.getid(xml.id)
        self.offset = int(xml.offset)/8
        Member.__init__(self, context)
    def resolve(self, context):
        self.type = context[context.getid(self.xml.type)]
        self.parent = context[context.getid(self.xml.context)]
        self.parent.fields.append(self)
    def get_fullname(self):
        return '%s.%s' % (self.parent.fullname, self.name)
    fullname = property(get_fullname)
    def __repr__(self):
        return '%s : %s' % (self.fullname, self.type.fullname) 
    def display(self, indent=0, offset=0):
        return '%s : %s @ %d' % (self.name, self.type.fullname, self.offset + offset)
    
class File(Member):
    def __init__(self, xml, context):
        self.id = xml.id
        self.name = xml.name.encode('utf-8')
        Member.__init__(self, context)
    def __repr__(self):
        return self.name
        
def generate(header, output, log):
    if log == '-': log = sys.stdout
    else: log = open(log,'w')
    output = open(output,'w')
    output.write('/* Generated code by ILCG for file "%s" */\n\n' % (header))
    headers = XMLFile(path=header)
    context = Context('')
    unknowns = {}
    for item in headers.root: 
        if not item._type == 'node': continue
        name = item._node.nodeName.encode('utf-8')
        if not name in globals():
            if not name in unknowns:
                unknowns[name] = True
            continue
        handler = globals()[name]
        handler(item, context)
    ids = {}
    for id in context: 
        context[id].resolve(context)
        item = context[id]
        if not item.__class__ in (Namespace, Class, Struct): continue
        if item.fullname in ids: print 'WARNING', item.fullname, type(ids[item.fullname]), type(item) #raise Exception(item.fullname)
        ids[item.fullname] = item
    debuggable = context.get('::illib::CodeGenerator::Debuggable')
    serializable = context.get('::illib::CodeGenerator::Serializable')
    illib = context.get('::illib')
    for id in context.Class:
        item = context[id]
        if len(item.namespace) > 2 and item.namespace[1] == illib: 
            pass
        log.write('%d:%s\n%s\n' % (id.id, item.fullname, item.display()))
        if item.inherits(debuggable):
            print 'Debuggable',item.fullname
        if item.inherits(debuggable): 
            print 'Serializable',item.fullname
        #print item.display()
    output.close()
            
    
generate(sys.argv[1], sys.argv[2], sys.argv[3])